package com.cognitran.cssvalidator.validation;

import java.io.File;
import java.util.Enumeration;

import org.w3c.css.css.DocumentParser;
import org.w3c.css.css.StyleSheet;
import org.w3c.css.parser.CssError;
import org.w3c.css.parser.CssErrorToken;
import org.w3c.css.parser.CssParseException;
import org.w3c.css.parser.Errors;
import org.w3c.css.util.ApplContext;
import org.w3c.css.util.InvalidParamException;
import org.w3c.css.util.Warning;
import org.w3c.css.util.Warnings;

import com.cognitran.cssvalidator.validation.model.CSSError;
import com.cognitran.cssvalidator.validation.model.CSSValidationResults;
import com.cognitran.cssvalidator.validation.model.CSSWarning;

/**
 * A CSS validator wrapper.
 * 
 * @author garethf
 */
public class CSSValidator 
{
	
	/** The W3C context object used for validation. */
	private final ApplContext ctx;

	
	/**
	 * Create a new CSS validator.
	 * 
	 * @param ctx The W3C application context used for validation.
	 */
	public CSSValidator(final ApplContext ctx)
	{
		this.ctx = ctx;
	}
	
	
	/**
	 * Parse the given source resource, and return it's validation results.
	 * 
	 * @param source The source resource that should be parsed.
	 * @return Validation results for the parsed response.
	 * 
	 * @throws Exception If there is a problem validating the CSS resource.
	 */
	public CSSValidationResults validate(final File source)
	throws Exception
	{
		// Create a CSS parser instance, and also a css DOM from the parser.
		final DocumentParser cssParser = new DocumentParser(ctx, source.toURI().toString());
		final StyleSheet css = cssParser.getStyleSheet();

		// Actually validate the CSS file that we are looking at.
		css.findConflicts(ctx);

		
		// FIXME !!!!!! warning level
		final CSSValidationResults results = new CSSValidationResults(ctx, css, 1);
		
		foldInValidationErrors(css, results);
		foldInValidationWarnings(css, results);
		
		return results;
	}


	/**
	 * Fold any CSS validation errors into the validation results collection.
	 * 
	 * @param css The parsed CSS that is interrogated for problems.
	 * @param results The validation results object that updates are written to.
	 */
	private void foldInValidationErrors(StyleSheet css, CSSValidationResults results) 
	{
		final Errors errors = css.getErrors();
		for (int i = 0 ; i < errors.getErrorCount() ; i++)
		{
			final CssError parseError = errors.getErrorAt(i);
			
			// Build our internal error, with translated message from the validation error.
			final CSSError error = decodeError(parseError, results.getContext());
			results.addError(error);
		}	
	}

	
	/**
	 * Fold any CSS validation warnings into the validation results collection.
	 * 
	 * @param css The parsed CSS that is interrogated for problems.
	 * @param results The validation results object that updates are written to.
	 */
	private void foldInValidationWarnings(final StyleSheet css, final CSSValidationResults results) 
	{
		final Warnings warnings = css.getWarnings();
		for (int i = 0 ; i < warnings.getWarningCount() ; i++)
		{
			final Warning parseWarning = warnings.getWarningAt(i);

			// Build our internal warning, with translated message from the validation warning.
			final CSSWarning warning = new CSSWarning();
			warning.setLine(parseWarning.getLine());
			warning.setContext(""); 
			// FIXME
			
//	parseWarning.getContext() ?????
			
			
			warning.setMessage(parseWarning.getWarningMessage());
			results.addWarning(warning);
		}
	}
	
	
	/**
	 * Decode a CSS validation error into our internal object.
	 * 
	 * @param error The internal W3C parsing error to decode.
	 * @param ac The internal W3C parsing context.
	 * @return The error object that contains the decoded validation error.
	 */
	public CSSError decodeError(final CssError error, final ApplContext ac)
	{
		final CSSError err = new CSSError();
		err.setLine(error.getLine());

		final Throwable e = error.getException();
		if (e instanceof CssParseException) 
		{
			decodeParseException(err, (CssParseException)e, ac);
		} 
	    else if (e instanceof InvalidParamException) 
	    {
	    	err.setMessage((e.getMessage() == null) ? "" : e.getMessage());
	    }
		else if (error instanceof CssErrorToken) 
		{
			final CssErrorToken terror = (CssErrorToken)error;
			err.setMessage(terror.getErrorDescription() + " : " + terror.getSkippedString());
		}
		else 
		{
			err.setMessage(ac.getMsg().getErrorString("unknown") + " " + e);
		}
		
		return err;
	}


	/**
	 * Decode a CSS parsing exception into an error container object.
	 * 
	 * @param err The result error object to update.
	 * @param error The CSS parsing exception to decode.
	 * @param ac The internal W3C parsing context.
	 */
	public void decodeParseException(CSSError err, CssParseException error, final ApplContext ac) 
	{
		if (error.getContexts() != null && error.getContexts().size() != 0) 
		{
			StringBuilder buf = new StringBuilder();
			for (Enumeration e = error.getContexts().elements() ; e.hasMoreElements() ; ) 
		    {
		        final Object ctx = e.nextElement();
				if (ctx != null) 
				{
				    buf.append(ctx);
				    if (e.hasMoreElements()) 
				    {
			        buf.append(", ");
			        }
			    }
			}
			
		    if (buf.length() != 0) 
			{
		    	err.setContext(buf.toString());
			}
		}	
			
			
		if ((error.getException() != null) && (error.getMessage() != null))
		{
			final StringBuilder b = new StringBuilder();

			if (error.isParseException()) 
			{
				b.append(error.getMessage());
			}
			else 
			{
				Exception ex = error.getException();
				if (ex instanceof NumberFormatException)
				{
					b.append(ac.getMsg().getGeneratorString("invalid-number"));
				}
				else 
				{
					b.append(ex.getMessage());
				}
			}	
			
			if (error.getSkippedString() != null) 
			{
				b.append("\n              ");
				b.append(error.getSkippedString());
			} 
			else if (error.getExp() != null) 
			{
				b.append(" : ").append(error.getExp().toStringFromStart());
			}
			
			err.setMessage(b.toString());
		}
		
	}
	
	
	
//	/**
//	 * Extract the context for the given CSS warning.
//	 * 
//	 * @param warning The CSS validation warning.
//	 * @return The context of the warning.
//	 */
//	public String getWarningContext(final Warning warning)
//	{
//		final StringBuilder b = new StringBuilder();
//
//		return b.toString();
//	}
//	

}
